This year, Santa brought little Bobby Tables a set of wires and bitwise logic gates! Unfortunately, little Bobby is a little under the recommended age range, and he needs help assembling the circuit.
Each wire has an identifier (some lowercase letters) and can carry a 16-bit signal (a number from 0 to 65535). A signal is provided to each wire by a gate, another wire, or some specific value. Each wire can only get a signal from one source, but can provide its signal to multiple destinations. A gate provides no signal until all of its inputs have a signal.
The included instructions booklet describe how to connect the parts together: x AND y -> z means to connect wires x and y to an AND gate, and then connect its output to wire z.
For example:
123 -> x means that the signal 123 is provided to wire x.
x AND y -> z means that the bitwise AND of wire x and wire y is provided to wire z.
p LSHIFT 2 -> q means that the value from wire p is left-shifted by 2 and then provided to wire q.
NOT e -> f means that the bitwise complement of the value from wire e is provided to wire f.
Other possible gates include OR (bitwise OR) and RSHIFT (right-shift). If, for some reason, you'd like to emulate the circuit instead, almost all programming languages (for example, C, JavaScript, or Python) provide operators for these gates.
For example, here is a simple circuit:
123 -> x
456 -> y
x AND y -> d
x OR y -> e
x LSHIFT 2 -> f
y RSHIFT 2 -> g
NOT x -> h
NOT y -> i
After it is run, these are the signals on the wires:
d: 72
e: 507
f: 492
g: 114
h: 65412
i: 65079
x: 123
y: 456
In little Bobby's kit's instructions booklet (provided as your puzzle input), what signal is ultimately provided to wire a?
In [21]:
import re
_LOGIC_OP = re.compile("([a-z0-9]+) (AND|OR) ([a-z0-9]+) -> ([a-z0-9]+)")
_SHIFT_OP = re.compile("([a-z0-9]+) (LSHIFT|RSHIFT) ([0-9]+) -> ([a-z0-9]+)")
_NOT_OP = re.compile("NOT ([a-z0-9]+) -> ([a-z0-9]+)")
_ASSIGN_OP = re.compile("([0-9]+) -> ([a-z0-9]+)")
_CONNECT_OP = re.compile("([a-z]+) -> ([a-z0-9]+)")
_OP_FUNC = {
'AND': lambda x, y: value(x) & value(y),
'OR': lambda x, y: value(x) | value(y),
'LSHIFT': lambda x, y: value(x) << y,
'RSHIFT': lambda x, y: value(x) >> y,
'NOT': lambda x: ~value(x)
}
# Extract a value from a function
def value(x):
if callable(x):
return value(x())
return x & 0xffff
# Extract wire
def get_wire(p_ins, wire):
if wire in p_ins:
v = p_ins[wire]
while callable(v):
v = v()
p_ins[wire] = v
return v
return int(wire)
# Functions to encapsulate values
def _logic_op(p_ins, op, wire_left, wire_right):
return lambda: _OP_FUNC[op](lambda: get_wire(p_ins, wire_left), get_wire(p_ins, wire_right))
def _shift_op(p_ins, op, shift, wire_left):
return lambda: _OP_FUNC[op](lambda: get_wire(p_ins, wire_left), int(shift))
def _not_op(p_ins, wire_left):
return lambda: _OP_FUNC['NOT'](lambda: get_wire(p_ins, wire_left))
def _connect_op(p_ins, wire_left):
return lambda: p_ins[wire_left]
# Parse and load all the instructions
def load_instructions(instructions):
p_ins = {}
for i in instructions:
# Logic operation
m = _LOGIC_OP.match(i)
if m:
wire_left, op, wire_right, wire = m.groups()
p_ins[wire] = _logic_op(p_ins, op, wire_left, wire_right)
continue
# Shift operation
m = _SHIFT_OP.match(i)
if m:
wire_left, op, shift, wire = m.groups()
p_ins[wire] = _shift_op(p_ins, op, shift, wire_left)
continue
# Not operation
m = _NOT_OP.match(i)
if m:
wire_left, wire = m.groups()
p_ins[wire] = _not_op(p_ins, wire_left)
continue
# Value connected to a wire
m = _ASSIGN_OP.match(i)
if m:
value, wire = m.groups()
p_ins[wire] = int(value)
continue
# Two wires connected
m = _CONNECT_OP.match(i)
if m:
wire_left, wire = m.groups()
p_ins[wire] = _connect_op(p_ins, wire_left)
continue
return p_ins
In [24]:
%%time
# Test example
ins = [
"123 -> x",
"456 -> y",
"x AND y -> d",
"x OR y -> e",
"x LSHIFT 2 -> f",
"y RSHIFT 2 -> g",
"NOT x -> h",
"NOT y -> i"
]
ains = load_instructions(ins)
print("d: 72 = ", value(ains['d']))
print("e: 507 = ", value(ains['e']))
print("f: 492 = ", value(ains['f']))
print("g: 114 = ", value(ains['g']))
print("h: 65412 = ", value(ains['h']))
print("i: 65079 = ", value(ains['i']))
print("x: 123 = ", value(ains['x']))
print("y: 456 = ", value(ains['y']))
In [26]:
%%time
# Load input
with open('day7/input.txt', 'rt') as fd:
ains = load_instructions(fd)
print("a = ", value(ains['a']))
In [28]:
# Load input
with open('day7/input.txt', 'rt') as fd:
ains = load_instructions(fd.readlines() + ['46065 -> b'])
print("a = ", value(ains['a']))